#include<iostream>
#include<memory>//智能指针的头文件
#include<sys/select.h>
#include<unistd.h>
#include"Socket.hpp"
#include"Log.hpp"

using namespace SocketModule;
using namespace LogModule;
class SelectSever
{
    const static int size = sizeof(fd_set) * 8;
    const static int defaultfd = -1;
public:
    SelectSever(uint16_t port):_listensock(std::make_unique<TcpSocket>()), _isruning(false)
    {
        _listensock->BuildTcpServerSocketMethod(port);//一键构建套接字


        //初始化辅助数组
        for(int i = 0; i < size; i++)
        {
            _fd_arry[i] = defaultfd;
        }
        //0号位置默认设置为监听的描述符,也就是说其余位置都是连接描述符
        _fd_arry[0] = _listensock->Fd();
    }
    void Start()
    {
        _isruning = true;
        while(_isruning)
        {
            //为什么要增加一个辅助数组,因为我们一轮监听了n个描述符,而下一轮的时候rfds可能会被打乱因为他是一个输入输出的参数,因此为了还原原来的状态我们使用数组来维护原状态
            //一定要使用数组吗,不一定但对于select来说使用其他的数据结构显得太重了,毕竟select可监控的文件描述符有限

            //这里不能直接accept应该先将监听链接的文件描述符让select管理起来,不然accept会进行阻塞等待
            fd_set rfds;//创建一个读文件描述符集合
            //这里的标志位不建议自己设置,因为不一定能设置对,而且我们写的代码也不一定具有跨平台性
            FD_ZERO(&rfds);//将标志位清空
            //FD_SET(_listensock->Fd(), &rfds);//将文件描述符设置进集合里面(我们这里使用的是基类指针所以只能用基类中有的方法)

            int maxfd = -1;
            //遍历辅助辅助数组,看一下需要监听哪些文件描述符
            for(int i = 0; i < size; i++)
            {
                if(_fd_arry[i] == defaultfd)
                {
                    continue;
                }
                FD_SET(_fd_arry[i], &rfds);
                //还要统计一下最大值这一块
                maxfd = std::max(_fd_arry[i], maxfd);
            }

            PrintFd();//打印看一下本轮监听的描述符有几个
            struct timeval timeout= {0,0};//测试非阻塞轮训的效果
            int n = select(maxfd+1, &rfds, nullptr, nullptr, nullptr);//这里将rfds的数据真正的拷贝到了内核
            // if(n > 0)
            // {
            //     std::cout << "有" << n << "个fd就绪了" << std::endl;
            // }
            // else if(n == 0)
            // {
            //     LOG(LogLevel::DEBUG) << "select timeout";
            // }
            // else
            // {
            //     LOG(LogLevel::DEBUG) << "select error";
            // }
            // sleep(1);

            switch(n)
            {
                case -1:
                    LOG(LogLevel::DEBUG) << "select error";
                    break;
                case 0:
                    LOG(LogLevel::DEBUG) << "select timeout";
                    break;
                default:
                    LOG(LogLevel::DEBUG) << "有事件就绪了";
                    Dispatcher(rfds);
                    break;
            }
        }
        _isruning = false;
    }
    void Dispatcher(fd_set& rfds)
    {
        //到了这里我们还要区分一下,是accept还是read读就绪这一块
        for(int i = 0; i <= size; i++)
        {
            if(_fd_arry[i] == defaultfd)
            {
                //改位置没有文件描述符
                continue;
            }
            //判断该文件描述符在监听到的集合里面
            if(FD_ISSET(_fd_arry[i], &rfds))
            {
                //判断是不是有新链接到来了 ,若不是新链接就是读取
                if(_fd_arry[i] == _listensock->Fd())
                {
                    //有新链接到来
                    LOG(LogLevel::DEBUG) << "进入连接模块";
                    Accepter();
                }
                else
                {
                    //有新的连接数据到来
                    LOG(LogLevel::DEBUG) << "进入读模块";
                    Recver(_fd_arry[i], i);
                }
            }
        }
    }
    void Accepter()
    {
        //这里还是不能直接进行fd因为我们仍然不能确立连接中是否有数据让我们读取,如果没有数据,仍然会进入到阻塞状态
        //到这里我们应该增添一个辅助数组了
        Inet_Addr client;
        int sockfd = _listensock->Accept(&client);
        if(sockfd >= 0)
        {
            LOG(LogLevel::DEBUG) << "get a new link, sockfd" << sockfd << "client" << client.StringAddr();

            //将创建好的连接存入到辅助数组中
            int pos = 0;
            for(;pos < size; pos++)
            {
                if(_fd_arry[pos] == defaultfd)
                {
                    break;
                }
            }
            if(pos == size)
            {
                //空间已满释放文件描述符
                LOG(LogLevel::DEBUG) << "_fd_arry is full";
                close(sockfd);
            }
            else
            {
                //找到空间
                _fd_arry[pos] = sockfd;
            }
        }
    }

    void Recver(int fd, int pos)
    {
        //可以开始读取数据了(一定能读到)
        char buffer[1024] = {0};
        ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            //读取成功
            std::cout << "client say@: " << buffer;
        }
        else if(n == 0)
        {
            //客户端退出
            LOG(LogLevel::DEBUG) << "client quit";
            _fd_arry[pos] = defaultfd;
            close(fd);
        }
        else
        {
            //读取失败
            LOG(LogLevel::DEBUG) << "read error";
            _fd_arry[pos] = defaultfd;
            close(fd);
        }
    }
    void PrintFd()
    {
        //打印监视的文件描述符
        std::cout << "_fd_arry[]: ";
        for(int i = 0; i < size; i++)
        {
            if(_fd_arry[i] == defaultfd)
            {
                continue;
            }
            std::cout << _fd_arry[i] << " ";
        }
        std::cout << std::endl;
    }
    //暂停服务器
    void Stop()
    {
        _isruning = false;
    }
    ~SelectSever()
    {
        ;
    }
private:
    std::unique_ptr<Socket> _listensock;//TCP基类监听端口的文件描述符
    bool _isruning;//服务的运行状态

    int _fd_arry[size];//构建一个辅助数组
};